#include "animation.h"

using namespace bt;

Animation::Animation()
{
	numFramesX = 0;
	numFramesY = 0;
	frames = NULL;
	
	curFrame = 0;
	numFrames = 0;
	startFrame = 0;
	endFrame = 0;
	
	refreshRate = 100;
	timer = NULL;
	
	flags = ANIM_DEFAULT;
}

Animation::Animation(Animation& rhs)
{
	timer = NULL; /* just in case it's not NULL */
	
	*this = rhs;
}

Animation::Animation(const char* filename,
					 Vector2f& frameDims,
					 Uint8 numFramesX, Uint8 numFramesY,
					 Uint8 flags)
{
	Texture tex(filename);
	
	curFrame = 0;
	startFrame = 0;
	endFrame = 0;
	
	setFlags(flags);
	
	refreshRate = 100;
	timer = NULL;
	
	setTexture(tex, frameDims, numFramesX, numFramesY);
}

Animation::Animation(Texture& tex,
					 Vector2f& frameDims,
					 Uint8 numFramesX, Uint8 numFramesY,
					 Uint8 flags)
{
	curFrame = 0;
	startFrame = 0;
	endFrame = 0;
	
	refreshRate = 100;
	timer = NULL;
	
	setFlags(flags);
	
	setTexture(tex, frameDims, numFramesX, numFramesY);
}
		
Animation::Animation(const char* filename,
					 Vector2f& frameDims, Vector2f& drawDims,
					 Uint8 numFramesX, Uint8 numFramesY,
					 Uint8 flags)
{
	Texture tex(filename);
	
	setFlags(flags);
	
	refreshRate = 100;
	timer = NULL;
	
	setTexture(tex, frameDims, drawDims, numFramesX, numFramesY);
}

Animation::Animation(Texture& tex,
					 Vector2f& frameDims, Vector2f& drawDims,
					 Uint8 numFramesX, Uint8 numFramesY,
					 Uint8 flags)
{
	setFlags(flags);
	
	refreshRate = 100;
	timer = NULL;
	
	setTexture(tex, frameDims, drawDims, numFramesX, numFramesY);
}

Animation::~Animation()
{
	/* Don't delete display lists
	   in case other animations are using them. */
	stop();
}

// Assignment Operator
Animation& Animation::operator=(Animation& rhs)
{
	texture = rhs.texture;
	frameDimensions = rhs.frameDimensions;
	numFramesX = rhs.numFramesX;
	numFramesY = rhs.numFramesY;
	frames = rhs.frames;
	
	curFrame = rhs.curFrame;
	numFrames = rhs.numFrames;
	startFrame = rhs.startFrame;
	endFrame = rhs.endFrame;
	
	dimensions = rhs.dimensions;
	
	refreshRate = rhs.refreshRate;
	/* timer should be NULL now: stopped */
	
	flags = rhs.flags;
}

// Accessors
Texture Animation::getTexture() { return texture; }

Uint32 Animation::getRefreshRate() { return refreshRate; }

Uint8 Animation::getFlags() { return flags; }

Animation Animation::getSubAnimation(int startFrame, int endFrame)
{
	Animation anim = *this;
	
	anim.frames = this->frames + startFrame;
	anim.numFrames = endFrame - startFrame;
	
	anim.setFrameBounds(0, anim.numFrames);
	
	return anim;
}

// Mutators
void Animation::setTexture(Texture& tex,
						   Vector2f& frameDims,
						   Uint8 numFramesX, Uint8 numFramesY)
{
	setTexture(tex, frameDims, frameDims, numFramesX, numFramesY);
}

void Animation::setTexture(Texture& tex,
						   Vector2f& frameDims, Vector2f& drawDims,
						   Uint8 numFramesX, Uint8 numFramesY)
{
	/* Delete old display lists if necessary */
	if(frames)
		freeLists();
	
	/*
	 * Make sure that all variables provided are good before continuing
	 */
	
	/* Check validity of numFrames */
	if((numFramesX > 0) && (numFramesY > 0))
	{
		this->numFramesX = numFramesX;
		this->numFramesY = numFramesY;
		
		numFrames = numFramesX * numFramesY;
		setFrameBounds(0, numFrames-1);
	}
	else
	{
		fprintf(stderr, "Animation: No display lists created, zero frames provided.\n");
		return;
	}
		
	/* Check if texture provided is valid */
	if(tex.getTexture())
	{
		texture = tex;
	}
	else
	{
		fprintf(stderr, "Animation: No display lists created, Invalid or NULL texture.\n");
		return;
	}
	
	/* Check frame and draw dimensions for negatives or zeros */
	if((frameDims.x > 0) && (frameDims.y > 0))
	{
		frameDimensions = frameDims;
	}
	else
	{
		fprintf(stderr, "Animation: No display lists created, Invalid frame dimensions.\n");
		return;
	}
	
	if((drawDims.x > 0) && (drawDims.y > 0))
	{
		dimensions = drawDims;
	}
	else
	{
		fprintf(stderr, "Animation: No display lists created, Invalid draw dimensions.\n");
		return;
	}
	
	/* Generate display lists for each frame */
	frames = glGenLists(numFrames);
	
	int frameCount = 0;
	
	Vector2f texDims = tex.getDimensions();
	Vector2f fracDims;
	
	fracDims.x = frameDims.x / texDims.x;	/* Convert frameDims to texture coordinates */
	fracDims.y = frameDims.y / texDims.y;
	
	float qwidth = drawDims.x / 2;
	float qheight = drawDims.y / 2;
	
	/* Reads from left to right, top to bottom of texture */
	for(float j = 0; j < numFramesY; j++)
	{
		for(float i = 0; i < numFramesX; i++)
		{
			/* Assume GL_MODELVIEW */
			glLoadIdentity();
			glNewList(frames + frameCount, GL_COMPILE);
				glBindTexture(GL_TEXTURE_2D, texture.getTexture());
				glPushMatrix();
				glBegin(GL_QUADS);
					glTexCoord2f(fracDims.x*i,
								 fracDims.y*j); /* Bottom Left */
					glVertex3f(-qwidth, -qheight,  0.0f);
					
					glTexCoord2f(fracDims.x*i + fracDims.x,
								 fracDims.y*j); /* Bottom Right */
					glVertex3f(qwidth, -qheight,  0.0f);
					
					glTexCoord2f(fracDims.x*i + fracDims.x,
								 fracDims.y*j + fracDims.y); /* Top Right */
					glVertex3f(qwidth,  qheight,  0.0f);
					
					glTexCoord2f(fracDims.x*i,
								 fracDims.y*j + fracDims.y); /* Top Left */
					glVertex3f(-qwidth,  qheight,  0.0f);
				glEnd();
				glPopMatrix();
			glEndList();
			
			frameCount++;
		}
	}
}

void Animation::setCurFrame(Uint8 frame)
{
	if((frame >= startFrame) && (frame <= endFrame))
		curFrame = frame;
	else
		reset();
}

void Animation::setFrameBounds(Uint8 start, Uint8 end)
{	
	startFrame = start;
	
	if(endFrame >= startFrame)
		endFrame = end;
	else
		endFrame = start;
}

void Animation::setRefreshRate(Uint32 rate)
{
	if(rate < 10)
		refreshRate = 10;
	else
		refreshRate = rate;
}

void Animation::setFlags(Uint8 flags)
{
	this->flags = flags;
	
	if((this->flags & ANIM_PINGPONG) > 0)
		this->flags |= ANIM_LOOP;	/* PINGPONG implies looping */
}

// Actions
void Animation::start()
{
	/* Check if already running */
	if(timer != NULL)
		stop();

	/* only start the animation if it's really going to animate */
	if(numFrames > 1)
		timer = SDL_AddTimer(refreshRate, &UpdateAnimation, this);
}

void Animation::stop()
{
	/* Check if already running */
	if(timer != NULL)
	{
		SDL_RemoveTimer(timer);
		timer = NULL;
	}
}

void Animation::reset()
{
	/* stop if not set to loop */
	if(!((flags & ANIM_LOOP) > 0))
		stop();
		
	if((flags & ANIM_REVERSE) > 0)
		curFrame = endFrame;
	else
		curFrame = startFrame;
	
	/* pingpong -> back to start even if in reverse */
	if((flags & ANIM_PINGPONG) > 0)
		curFrame = startFrame;
}

void Animation::draw()
{	
	glCallList(frames + curFrame);
}

void Animation::draw(float posx, float posy)
{
	/*
	 * Use quarter dimensions of the texture to enable
	 * the image to be drawn with origin at top left
	 * but still rotate around the image center
	 */
	float qwidth = dimensions.x/2;
	float qheight = dimensions.y/2;
	
	/* Assume GL_MODELVIEW */
	glPushMatrix();
	
		glTranslatef(posx + qwidth, posy + qheight, 0.0f);
		
		draw();
	
	glPopMatrix();
}

void Animation::freeLists()
{
	/* Delete display lists for
	   individual frames/quads */
	if(glIsList(frames))
		glDeleteLists(frames, numFrames);
	
	numFrames = 0;
}

// Inhereted from XMLObject
bool Animation::loadFromXML(xmlDoc* doc, xmlNode* node)
{
	/* Set all variables to defaults */
	Animation anim;
	*this = anim;
	
	/* Check that root node is an animation */
	if(xmlStrcmp(node->name, (xmlChar*) "animation"))
	{
		fprintf(stderr, "Animation: XML: root node != animation\n");
		return false;
	}
	
	/*
	 * Get properties of <animation>
	 */
	Uint8 newStartFrame = XML_getPropInt(node, "startframe");
	Uint8 newEndFrame = XML_getPropInt(node, "endframe");
	
	Uint32 newRefreshRate = XML_getPropInt(node, "refreshrate");
	if(newRefreshRate == 0)
		setRefreshRate(100);
	else
		setRefreshRate(XML_getPropInt(node, "refreshrate"));
	
	/*
	 * Tokenize the flags property into flag values
	 * then set the flags of the animation
	 */
	Uint8 newFlags = ANIM_DEFAULT;
	
	std::string flagsStr = XML_getPropString(node, "flags");
	
	if(!flagsStr.empty())
	{
		while(!flagsStr.empty())
		{
			std::string flag = cutFirstToken(flagsStr, "|");
			
			/* Do nothing for ANIM_DEFAULT/ANIM_FORWARD flags */
			
			if(flag == "ANIM_REVERSE")
				newFlags |= ANIM_REVERSE;
			
			if(flag == "ANIM_LOOP")
				newFlags |= ANIM_LOOP;
			
			if(flag == "ANIM_PINGPONG")
				newFlags |= ANIM_PINGPONG;
		}
	}
	
	setFlags(newFlags);
	
	/*
	 * Loop through children nodes to find texture
	 */
	node = node->xmlChildrenNode;
	while(node)
	{
		if(!xmlStrcmp(node->name, (xmlChar*) "texture"))
		{	/* Found <texture>, now get vars to send to setTexture */
			std::string filename = XML_getContentString(node->xmlChildrenNode);
			Texture tex(filename.c_str());
			
			Vector2f frameDims;
			frameDims.x = XML_getPropFloat(node, "framewidth");
			frameDims.y = XML_getPropFloat(node, "frameheight");
			
			Vector2f drawDims;
			drawDims.x = XML_getPropFloat(node, "drawwidth");
			drawDims.y = XML_getPropFloat(node, "drawheight");
			
			int numFramesX = XML_getPropInt(node, "numframesx");
			int numFramesY = XML_getPropInt(node, "numframesy");
			
			/* Check if valid drawDims were provided */
			if((drawDims.x == 0) || (drawDims.y == 0))
				setTexture(tex, frameDims, numFramesX, numFramesY);
			else
				setTexture(tex, frameDims, drawDims, numFramesX, numFramesY);
			
			/* Set the frame bounds now that we have a valid texture */
			setFrameBounds(newStartFrame, newEndFrame);
		}
			
		node = node->next;
	}
	
	return true;
}

void Animation::writeToXML(xmlDoc* doc, xmlNode* node)
{
}

// The Super Duper Update Animation function!
Uint32 bt::UpdateAnimation(Uint32 interval, void* fakeThis)
{
	Animation* anim = (Animation*)fakeThis;
	
	if((anim->flags & ANIM_REVERSE) > 0)
	{
		anim->curFrame--;

		if(anim->curFrame <= anim->startFrame)
		{
			if((anim->flags & ANIM_LOOP) > 0)
			{
				if(anim->curFrame < anim->startFrame)
				{
					if((anim->flags & ANIM_PINGPONG) > 0)
					{
						anim->flags ^= ANIM_REVERSE;
						anim->curFrame = anim->startFrame+1;
					}
					else
						anim->curFrame = anim->endFrame;
				}
			}
			else
			{
				anim->stop();
				return 0;
			}
		}
	}	/* End reverse */
	else
	{
		anim->curFrame++;
		
		if(anim->curFrame >= anim->endFrame)
		{
			if((anim->flags & ANIM_LOOP) > 0)
			{
				if(anim->curFrame > anim->endFrame)
				{
					if((anim->flags & ANIM_PINGPONG) > 0)
					{
						anim->flags |= ANIM_REVERSE;
						anim->curFrame = anim->endFrame-1;
					}
					else
						anim->curFrame = anim->startFrame;
				}
			}
			else
			{
				anim->stop();
				return 0;
			}
		}
	}	/* End forward */
	
	return interval;
}
